// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <nebula/core/extension_type.h>
#include <nebula/types/type.h>
#include <turbo/base/macros.h>
#include <nebula/core/visitor_generate.h>

namespace nebula {

#define TYPE_VISIT_INLINE(TYPE_CLASS)                                            \
      case TYPE_CLASS##Type::type_id:                                                \
        return visitor->Visit(turbo::checked_cast<const TYPE_CLASS##Type&>(type), \
                              std::forward<ARGS>(args)...);

    /// \brief Calls `visitor` with the corresponding concrete type class
    ///
    /// \tparam VISITOR Visitor type that implements Visit() for all Nebula types.
    /// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after
    /// the `type` argument
    /// \return turbo::Status
    ///
    /// A visitor is a type that implements specialized logic for each Nebula type.
    /// Example usage:
    ///
    /// ```
    /// class ExampleVisitor {
    ///   turbo::Status Visit(const nebula::Int32Type& type) { ... }
    ///   turbo::Status Visit(const nebula::Int64Type& type) { ... }
    ///   ...
    /// }
    /// ExampleVisitor visitor;
    /// visit_type_inline(some_type, &visitor);
    /// ```
    template<typename VISITOR, typename... ARGS>
    inline turbo::Status visit_type_inline(const DataType &type, VISITOR *visitor, ARGS &&... args) {
        switch (type.id()) {
            NEBULA_GENERATE_FOR_ALL_TYPES(TYPE_VISIT_INLINE);
            default:
                break;
        }
        return turbo::unimplemented_error("Type not implemented");
    }

#undef TYPE_VISIT_INLINE

#define TYPE_VISIT_INLINE(TYPE_CLASS)                          \
      case TYPE_CLASS##Type::type_id:                              \
        return std::forward<VISITOR>(visitor)(                     \
            turbo::checked_cast<const TYPE_CLASS##Type&>(type), \
            std::forward<ARGS>(args)...);

    /// \brief Call `visitor` with the corresponding concrete type class
    /// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after
    /// the `type` argument
    ///
    /// Unlike visit_type_inline which calls `visitor.Visit`, here `visitor`
    /// itself is called.
    /// `visitor` must support a `const DataType&` argument as a fallback,
    /// in addition to concrete type classes.
    ///
    /// The intent is for this to be called on a generic lambda
    /// that may internally use `if constexpr` or similar constructs.
    template<typename VISITOR, typename... ARGS>
    inline auto visit_type(const DataType &type, VISITOR &&visitor, ARGS &&... args)
    -> decltype(std::forward<VISITOR>(visitor)(type, args...)) {
        switch (type.id()) {
            NEBULA_GENERATE_FOR_ALL_TYPES(TYPE_VISIT_INLINE);
            default:
                break;
        }
        return std::forward<VISITOR>(visitor)(type, std::forward<ARGS>(args)...);
    }

#undef TYPE_VISIT_INLINE

#define TYPE_ID_VISIT_INLINE(TYPE_CLASS)                              \
      case TYPE_CLASS##Type::type_id: {                                   \
        const TYPE_CLASS##Type* concrete_ptr = nullptr;                   \
        return visitor->Visit(concrete_ptr, std::forward<ARGS>(args)...); \
      }

    /// \brief Calls `visitor` with a nullptr of the corresponding concrete type class
    ///
    /// \tparam VISITOR Visitor type that implements Visit() for all Nebula types.
    /// \tparam ARGS Additional arguments, if any, will be passed to the Visit function after
    /// the `type` argument
    /// \return turbo::Status
    template<typename VISITOR, typename... ARGS>
    inline turbo::Status visit_type_id_inline(Type::type id, VISITOR *visitor, ARGS &&... args) {
        switch (id) {
            NEBULA_GENERATE_FOR_ALL_TYPES(TYPE_ID_VISIT_INLINE);
            default:
                break;
        }
        return turbo::unimplemented_error("Type not implemented");
    }

#undef TYPE_ID_VISIT_INLINE

}  // namespace nebula
